/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.client.virtual.model.jcr.nodetype;

import java.io.Serializable;
import java.util.HashMap;

import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.ValueFormatException;
import offset.nodes.client.virtual.model.jcr.QName;
import offset.nodes.client.virtual.model.jcr.SimpleValue;

/**
 * This class implements the <code>PropDef</code> interface and additionally
 * provides setter methods for the various property definition attributes.
 */
public class PropertyDefinition extends ItemDefinition implements Serializable {

    /**
     * The required type.
     */
    private int requiredType = PropertyType.UNDEFINED;
    /**
     * The 'multiple' flag
     */
    private boolean multiple = false;
    /**
     * The identifier of this property definition. 
     */
    private String id = null;
    /**
     * The value constraints, if any, for this property
     */
    ValueConstraintDefinition[] valueConstraints = new ValueConstraintDefinition[0];

    /**
     * Default values
     */
    Value[] defaultValues = null;

    /**
     * Default constructor.
     */
    public PropertyDefinition() {
    }
    
    public Object clone() throws CloneNotSupportedException {
        PropertyDefinition clone = (PropertyDefinition) super.clone();
        
        clone.valueConstraints = new ValueConstraintDefinition[valueConstraints.length];
        for (int i=0; i<valueConstraints.length; i++)
            clone.valueConstraints[i] = new ValueConstraintDefinition(valueConstraints[i].getDefinition());
        
        return clone;
    }

    /**
     * Sets the required type
     *
     * @param requiredType
     */
    public void setRequiredType(int requiredType) {
        // reset id field in order to force lazy recomputation of identifier
        id = null;
        this.requiredType = requiredType;
    }

    /**
     * Sets the 'multiple' flag.
     *
     * @param multiple
     */
    public void setMultiple(boolean multiple) {
        // reset id field in order to force lazy recomputation of identifier
        id = null;
        this.multiple = multiple;
    }

    /**
     * {@inheritDoc}
     */
    public void setDeclaringNodeType(QName declaringNodeType) {
        // reset id field in order to force lazy recomputation of identifier
        id = null;
        super.setDeclaringNodeType(declaringNodeType);
    }

    /**
     * {@inheritDoc}
     */
    public void setName(QName name) {
        // reset id field in order to force lazy recomputation of identifier
        id = null;
        super.setName(name);
    }

    /**
     * {@inheritDoc}
     */
    public void setAutoCreated(boolean autoCreated) {
        // reset id field in order to force lazy recomputation of identifier
        id = null;
        super.setAutoCreated(autoCreated);
    }

    /**
     * {@inheritDoc}
     */
    public void setOnParentVersion(int onParentVersion) {
        // reset id field in order to force lazy recomputation of identifier
        id = null;
        super.setOnParentVersion(onParentVersion);
    }

    /**
     * {@inheritDoc}
     */
    public void setProtected(boolean writeProtected) {
        // reset id field in order to force lazy recomputation of identifier
        id = null;
        super.setProtected(writeProtected);
    }

    /**
     * {@inheritDoc}
     */
    public void setMandatory(boolean mandatory) {
        // reset id field in order to force lazy recomputation of identifier
        id = null;
        super.setMandatory(mandatory);
    }

    //--------------------------------------------------------------< PropDef >
    /**
     * {@inheritDoc}
     * <p/>
     * The identifier is computed based on the characteristics of this property
     * definition, i.e. modifying attributes of this property definition will
     * have impact on the identifier returned by this method.
     */
    public String getId() {
        if (id == null)
            // generate new identifier based on this property definition
            id = toString();
        return id;
    }

    /**
     * {@inheritDoc}
     */
    public int getRequiredType() {
        return requiredType;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isMultiple() {
        return multiple;
    }

    /**
     * {@inheritDoc}
     *
     * @return always <code>false</code>
     */
    public boolean definesNode() {
        return false;
    }

    /**
     * Return the value constraints for this property
     * @return The constraints
     */
    public ValueConstraintDefinition[] getValueConstraints() {
        return valueConstraints;
    }

    /**
     * Set the value constraints for this property
     * @param valueConstraints The constraints
     */
    public void setValueConstraints(ValueConstraintDefinition[] valueConstraints) {
        this.valueConstraints = valueConstraints;
    }

    /**
     * Set the default values. Currently, only String values
     * are supported
     *
     * @param defaultValues
     */
    public void setDefaultValues(Value[] defaultValues) throws RepositoryException {
        SimpleValue[] values = new SimpleValue[defaultValues.length];

        for(int i = 0; i < values.length; i++)
            values[i] = new SimpleValue(defaultValues[i].getString());

        this.defaultValues = values;
    }

    public Value[] getDefaultValues() {
        return defaultValues;
    }

    //-------------------------------------------< java.lang.Object overrides >
    /**
     * Compares two property definitions for equality. Returns <code>true</code>
     * if the given object is a property defintion and has the same attributes
     * as this property definition.
     *
     * @param obj the object to compare this property definition with
     * @return <code>true</code> if the object is equal to this property definition,
     *         <code>false</code> otherwise
     * @see Object#equals(Object)
     */
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj instanceof PropertyDefinition) {
            PropertyDefinition other = (PropertyDefinition) obj;
            return super.equals(obj) && requiredType == other.requiredType && multiple == other.multiple;
        }
        return false;
    }

    /**
     * Returns zero to satisfy the Object equals/hashCode contract.
     * This class is mutable and not meant to be used as a hash key.
     *
     * @return always zero
     * @see Object#hashCode()
     */
    public int hashCode() {
        return 0;
    }
    
    protected static HashMap<Integer,String> typeToString = new HashMap<Integer,String>();
    protected static HashMap<String,Integer> stringToType = new HashMap<String,Integer>();
    
    static {
        typeToString.put(new Integer(PropertyType.UNDEFINED), PropertyType.TYPENAME_UNDEFINED );
        stringToType.put(PropertyType.TYPENAME_UNDEFINED, new Integer(PropertyType.UNDEFINED)  );
        typeToString.put(new Integer(PropertyType.STRING), PropertyType.TYPENAME_STRING );
        stringToType.put(PropertyType.TYPENAME_STRING, new Integer(PropertyType.STRING)  );
        typeToString.put(new Integer(PropertyType.DATE), PropertyType.TYPENAME_DATE );
        stringToType.put(PropertyType.TYPENAME_DATE, new Integer(PropertyType.DATE)  );
        typeToString.put(new Integer(PropertyType.REFERENCE), PropertyType.TYPENAME_REFERENCE );
        stringToType.put(PropertyType.TYPENAME_REFERENCE, new Integer(PropertyType.REFERENCE)  );
        typeToString.put(new Integer(PropertyType.BINARY), PropertyType.TYPENAME_BINARY );
        stringToType.put(PropertyType.TYPENAME_BINARY, new Integer(PropertyType.BINARY)  );
        typeToString.put(new Integer(PropertyType.LONG), PropertyType.TYPENAME_LONG );
        stringToType.put(PropertyType.TYPENAME_LONG, new Integer(PropertyType.LONG)  );
        typeToString.put(new Integer(PropertyType.DOUBLE), PropertyType.TYPENAME_DOUBLE );
        stringToType.put(PropertyType.TYPENAME_DOUBLE, new Integer(PropertyType.DOUBLE)  );
        typeToString.put(new Integer(PropertyType.NAME), PropertyType.TYPENAME_NAME );
        stringToType.put(PropertyType.TYPENAME_NAME, new Integer(PropertyType.NAME)  );
        typeToString.put(new Integer(PropertyType.PATH), PropertyType.TYPENAME_PATH );
        stringToType.put(PropertyType.TYPENAME_PATH, new Integer(PropertyType.PATH)  );
        typeToString.put(new Integer(PropertyType.BOOLEAN), PropertyType.TYPENAME_BOOLEAN );
        stringToType.put(PropertyType.TYPENAME_BOOLEAN, new Integer(PropertyType.BOOLEAN)  );
    }

    public int type(String type) {
        Integer result = stringToType.get(type);
        if ( result == null)
            return PropertyType.UNDEFINED;
        return result.intValue();
    }
    
    public String type(int type) {
        String result = typeToString.get(type);
        if ( result == null)
            return PropertyType.TYPENAME_UNDEFINED;
        return result;
    }

}
